package com.xxx.gateway.configure;

import cn.hutool.jwt.JWTUtil;
import com.xxx.common.base.bo.UserTokenBO;
import com.xxx.common.base.constants.MdcKey;
import com.xxx.common.base.constants.RedisKey;
import com.xxx.common.base.util.TokenUtil;
import com.xxx.common.redis.service.RedisService;
import com.xxx.gateway.properties.WhiteListProperties;
import jakarta.annotation.PostConstruct;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPatternParser;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

@Slf4j
@Component
@AllArgsConstructor
public class AuthenticationFilter implements GlobalFilter, Ordered {

    private final RedisService redisService;

    private final WhiteListProperties whiteListProperties;

    private final PathPatternParser pathPatternParser = new PathPatternParser();

    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

    /*** 菜单地址 - 角色列表 */
    private static final AtomicReference<ConcurrentHashMap<String, Set<Long>>> AUTH_REF = new AtomicReference<>(new ConcurrentHashMap<>());
    @PostConstruct
    public void init() {
        log.info("缓存redis角色权限开始");

        // 初始加载缓存
        updateCache();

        // 每5秒更新一次缓存
        scheduler.scheduleAtFixedRate(this::updateCache, 5, 5, TimeUnit.SECONDS);
    }


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String tenantId = request.getHeaders().getFirst(MdcKey.TENANT_ID);
        String path = request.getPath().toString();
        String authHeader = request.getHeaders().getFirst("Authorization");
        String token = null;
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            token = authHeader.substring(7).trim(); // 去除 "Bearer " 并清理前后空格
        }

        // TenantId基本验证
        if (StringUtils.isBlank(tenantId)) {
            return unauthorizedResponse(exchange, "tenantId 缺失");
        }

        // 白名单检查
        if (isLoginWhiteList(path)) {
            ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
                    .header(MdcKey.TENANT_ID, tenantId)  // 显式设置 tenantId
                    .build();
            return chain.filter(exchange.mutate().request(mutatedRequest).build());
        }

        // Token基本验证
        if (StringUtils.isBlank(token)) {
            return unauthorizedResponse(exchange, "token 缺失");
        }
        if (!JWTUtil.verify(token, "java-projects".getBytes())) {
            return unauthorizedResponse(exchange, "token 校验失败");
        }

        // 解析Token
        UserTokenBO userTokenBO = TokenUtil.parseToken(token);

        // 检查过期时间
        if (userTokenBO.getExpireTime() != null && System.currentTimeMillis() > userTokenBO.getExpireTime()) {
            return unauthorizedResponse(exchange, "身份认证过期，请重新登录");
        }

        // 权限检查
        if (!isAuthWhiteList(path)) {
            Map<String, Set<Long>> currentAuthMap = AUTH_REF.get(); // 获取当前最新快照
            Set<Long> allowedRoles = currentAuthMap.get(path);
            if (CollectionUtils.isEmpty(allowedRoles)) {
                return unauthorizedResponse(exchange, "path 菜单不存在");
            }
            if (!hasRequiredRole(userTokenBO.getRoleIds(), allowedRoles)) {
                return unauthorizedResponse(exchange, "无权访问接口");
            }
        }

        // 将用户信息添加到请求头
        ServerHttpRequest mutatedRequest = request.mutate()
                .header(MdcKey.USER_TOKEN, token)
                .header(MdcKey.TENANT_ID, tenantId)
                .build();
        return chain.filter(exchange.mutate().request(mutatedRequest).build());
    }


    private boolean isLoginWhiteList(String path) {
        return whiteListProperties.getLoginWhiteList().stream().anyMatch(pattern ->
                pathPatternParser.parse(pattern).matches(PathContainer.parsePath(path))
        );
    }

    private boolean isAuthWhiteList(String path) {
        return whiteListProperties.getAuthWhiteList().stream().anyMatch(pattern ->
                pathPatternParser.parse(pattern).matches(PathContainer.parsePath(path))
        );
    }

    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String message) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json");

        String body = String.format("{\"code\": 401, \"message\": \"%s\"}", message);
        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }

    private void updateCache() {
        try {
            Map<String, Set<Long>> newData = redisService.getCacheMap(RedisKey.ROLE_MENU_MAP);
            if (!CollectionUtils.isEmpty(newData)) {
                AUTH_REF.set(new ConcurrentHashMap<>(newData)); // 原子替换
            }
        } catch (Exception e) {
            log.error("获取redis角色权限异常", e);
        }
    }

    private boolean hasRequiredRole(List<Long> userRoles, Set<Long> requiredRoles) {
        return userRoles.stream().anyMatch(requiredRoles::contains);
    }

    @Override
    public int getOrder() {
        return -100; // 高优先级
    }
}